0%

与 TextView 相关的知识点

TextView以及其子类

先说两个小知识

1.TextView 实现跑马灯效果

1
2
3
4
5
6
<TextView
android:singleLine="true" // 必须
android:marqueeRepeatLimit="marquee_forever" //是否永远循环
android:focusable="true" // 必须
android:focusableInTouchMode="true" // 必须
android:ellipsize="marquee""/> // 必须

2.TextView 的字体设置
字体设置可以在 fonts.xml 文件中查找,其定义如下

1
2
3
4
5
6
7
8
9
<family>
<nameset>
<name>cursive</name>
</nameset>
<fileset>
<file>DancingScript-Regular.ttf</file>
<file>DancingScript-Bold.ttf</file>
</fileset>
</family>

使用时指定名称就可以了

1
app:family="casual"  //这是一个 string 类型的属性

读取该值后可以创建字体

1
Typeface.create(mFamily, Typeface.NORMAL)

TextView的绘制:Layout

TextView 在绘制时会先绘制四边的 drawable 。

1
2
3
4
android:drawableLeft="@drawable/left"
android:drawableTop="@drawable/top"
android:drawableBottom="@drawable/bottom"
android:drawableRight="@drawable/right"

其余的绘制工作就交给 Layout 类了,正常情况下采用 StaticLayout,而 EditText 采用 DynamicLayout。

Layout 负责文本的布局和绘制,绘制效果取决于构造它的诸多参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @param text the text to render
* @param paint the default paint for the layout. Styles can override
* various attributes of the paint.
* @param width the wrapping width for the text.
* @param align whether to left, right, or center the text. Styles can
* override the alignment.
* @param spacingMult factor by which to scale the font size to get the
* default line spacing
* @param spacingAdd amount to add to the default line spacing
*/
protected Layout(CharSequence text, TextPaint paint,
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingMult, float spacingAdd)

绘制方法包括两步,即按行绘制文本即行背景。

Layout 有很多获取和设置布局的信息,如

1.获得某画笔类TextPaint下的文本宽度

1
public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint)

2.获得文本行数以及某一行的信息

1
2
3
4
public int getLineCount();
public int getLineTop(int line);
public int getLineDescent(int line);
public abstract int getLineStart(int line);

实际上完全可以覆盖掉默认的绘制方法,利用Layout实现绘制效果,如

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
Layout layout = getLayout();
int count = layout.getLineCount();
for (int i = 0; i < count; i++) {
float lineLeft = layout.getLineLeft(i);
float lineBaseline = layout.getLineBaseline(i);
String lineText = getText().subSequence(lineStart, lineEnd).toString();
canvas.drawText(lineText, lineLeft, lineBaseline, getPaint());
}
}

这个效果和默认的绘制效果至少看上去完全一致(实际丢失了对marquee标记的处理),使用 Layout 还能够达成更多效果。

EditText

EditText 实际上完完全全就是 TextView,只不过它采用的样式如下

1
2
3
4
5
6
7
8
9
10
11
<style name="Widget.EditText">
<item name="focusable">true</item>
<item name="focusableInTouchMode">true</item>
<item name="clickable">true</item>
<item name="background">?attr/editTextBackground</item>
<item name="textAppearance">?attr/textAppearanceMediumInverse</item>
<item name="textColor">?attr/editTextColor</item>
<item name="gravity">center_vertical</item>
<item name="breakStrategy">simple</item>
<item name="hyphenationFrequency">normal</item>
</style>

其背景 drawable 在正常状态下是白色的.9图(@drawable/textfield_default),通过设置如下属性 TextView 亦可以选择文本供剪贴板使用

1
android:textIsSelectable="true"

TextView 和 EditText 所得的文本为 EditText 类,这是一个继承了很多文本处理接口的类,功能强大,例如可以设置过滤器 InputFilter,数字输入框,号码框就是靠其实现的。

AutoCompleteTextView 继承 EditText ,本是一个带 PopupWindow(带 ListView) 的 EditText,获取焦点时显示 PopupWindow。

Button

Button 实际上也完完全全就是 TextView,只不过它使用了默认的样式,改变了外观,所采用的样式可以在 themes 文件中找到,如下

1
2
3
4
5
6
7
8
<style name="Widget.Button">
<item name="background">@drawable/btn_default</item>
<item name="focusable">true</item>
<item name="clickable">true</item>
<item name="textAppearance">?attr/textAppearanceSmallInverse</item>
<item name="textColor">@color/primary_text_light</item>
<item name="gravity">center_vertical|center_horizontal</item>
</style>

其中影响按钮外观最大的就是背景 Drawable,默认情况下为一个 SelectDrawable,在正常状态下是灰色的.9图(位于res\drawable-mdpi.btn_default_normal.9.png),并在不同的状态/主题下采用不同颜色的.9图。

android 默认提供了很多按钮的 style,如

1
style="?android:attr/imageButtonStyle"

CompoundButton

CompoundButton 是一种特殊的 Button,从功能上将它只有两种状态,即是否 isChecked;从实现上看,它采用了新 Drawable 来表示 checked 状态,并重写了 onDraw 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void onDraw(Canvas canvas) {
//1.首先绘制 TextView 和 背景 Drawable
super.onDraw(canvas);
buttonDrawable.setBounds(left, top, right, bottom);
//2.再绘制 buttonDrawable,注意这里的 trick,要处理 scrollX/scrollY
if (buttonDrawable != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (scrollX == 0 && scrollY == 0) {
buttonDrawable.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
buttonDrawable.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}

因此我们知道改变 CompoundButton 既可以改变其外观,在XML上属性为

1
android:button="@null" // 即buttonDrawable为空

在 java 代码上

1
public void setButtonDrawable(@DrawableRes int resId)

子类 CheckBox玩玩全全就是 CompoundButton,另一个子类 Switch 则较为复杂。

发生点击事件后处理如下

1
2
3
4
5
6
7
8
@Override
public boolean performClick() {
toggle();
return super.performClick();
}
public void toggle() {
setChecked(!mChecked);
}

Switch

Switch 上除了 button 参数,另有两个 drawable,底层的叫 “track”,上层的叫“thumb”,上层面积是底层的一半左右,通过遮盖和移位来表示 checked 状态。此外 checked 时的文本叫做 “textOn”,而反之叫做 “textOff”。

注意此时 Button Drawable 依然有效。

在实现上,除了重写 onDraw 方法外,还要处理 “thumb” 区域的触摸事件,以完成状态切换。

因为“thumb” 是飘在 “track”上左右移动的,因此切换的动画是用属性动画完成的。

一些与 Text 相关的库

集大成者 : HTextView

HTextView 这个库实现的效果华丽,代码的风格和扩展性也非常好,而且作者将不同的效果仅仅分包,引入某种效果的体积极小,实在是不可多得的优质库。

1
2
3
4
public abstract class HTextView extends TextView {
public abstract void setProgress(float progress);
public abstract void animateText(CharSequence text);
}

1.Line 效果实质是在文本之外定点,描边,画线,这是附加性质的绘制。

2.Fade 效果是自发绘制文本,这里要利用 TextView 中 Layout 这个类。首先**均匀的挑出透明的字符位置,组成 alphaList。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void drawFrame(Canvas canvas) {
Layout layout = mHTextView.getLayout();
int gapIndex = 0;
for (int i = 0; i < layout.getLineCount(); i++) {
int lineStart = layout.getLineStart(i);
int lineEnd = layout.getLineEnd(i);
float lineLeft = layout.getLineLeft(i);
float lineBaseline = layout.getLineBaseline(i);
String lineText = mText.subSequence(lineStart, lineEnd).toString();
for (int c = 0; c < lineText.length(); c++) {
int alpha = alphaList.get(gapIndex);
mPaint.setAlpha((int) ((255 - alpha) * progress + alpha));
String str = lineText.substring(j, j+1);
canvas.drawText(str, lineLeft, lineBaseline, mPaint);
lineLeft += getPaint().measureText(str);
}
}

这里要准确测量每一个字符的宽度。

3.打字机效果的实现十分简单

1
2
3
public void setProgress(float progress) {
setText(mText.subSequence(0, (int) (mText.length() * progress)));
}

4.彩虹效果:通过操作Shader的矩阵完成染色。

1
2
3
4
5
6
7
8
9
10
protected void onDraw(Canvas canvas) {
if (mMatrix == null) {
mMatrix = new Matrix();
}
mTranslate += colorSpeed;
mMatrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(mMatrix);
super.onDraw(canvas);
postInvalidateDelayed(100);
}

5.Scale 效果:利用 Layout 重新定义了绘制方法,以重绘新旧字符串。重复的字符移位,其余的收缩。以下两种情况亦然。

Shimmer

一般情况下闪光效果和 HTextView 一样是采用渐变的效果达成的,对画笔进行设置,使渐变的位置发生偏移,即产生闪光效果。这里只需要改变 Paint 取像素的方式即可

1
2
mGradient.setLocalMatrix(mMatrix);
getPaint().setShader(mGradient);

这里将原生画笔的像素提供方式改变了,它只与绘制的内容文本有关,与背景没有干扰。如果要取消闪光效果,可以将其设为null。

但要达到较好的效果还需要

1.仔细的设置渐变的参数,渐变的设置如下

1
2
3
4
5
6
7
mGradient = new LinearGradient(getWidth(), 0, 0, 0,
new int[]{
getCurrentTextColor(),
0xFFFFFFFF,
getCurrentTextColor()},
new float[]{0, 0.5f, 1},
Shader.TileMode.CLAMP);

2.处理矩阵的变化已达到动画效果。

1
2
3
mMatrix.setTranslate((mProcess-0.5f)*getMeasuredWidth(), 0);//这里要回退半个屏幕距离,和渐变的设置有关
mMGradient.setLocalMatrix(mMatrix);
super.onDraw(canvas);

facebook的这个 Shimmer 库可贵的是将闪光效果扩展到布局上去了,其实他的做法也是通过Shader,但首先要将 布局内容转为 Bitmap,再在绘制 Bitmap 的时候应用上述这一套。

ReadMoreTextView

这里文本设置两种模式:显示完全和保留显示(最多显示240字);

ReadMoreTextView 是利用 ClickSpan 来设置最后的提示文本,ExpandableTextView这个库则是另外的思路,它本质上是一个 Linear 布局,需要重新计算尺寸,以及在点击事件发生时播放动画。相比之下前者的实现更简洁,但在切换时没有实现动画效果。

实现这个效果,我们要重写如下方法

1
2
3
4
5
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(getDisplayText(text), type);
setMovementMethod(LinkMovementMethod.getInstance());//使得TextView能够响应动作
}

使用 SpannableStringBuilder 来改造原来的 text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private CharSequence getDisplayText(final CharSequence text) {
int len = text.length();
if (len < 240) {
return text;
}
SpannableStringBuilder builder = generateText(text, isShowMore);
builder.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
isShowMore = !isShowMore;
setText(text);
}
}, start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
} ;
return builder;
}

字体设置

1
<TextView fontPath="fonts/MyFont.ttf"/>

这里 TextView 可以使用新的标签,这是通过代理 Context 来实现的,这里主要是改造 LayoutInflater 。改造 Factory2 和 Factory完成。